<?php

namespace App\Supports\Auth\Alipay;

use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AlipayAuthBase
{
	protected $authUri = 'https://openapi.alipay.com/gateway.do';

	private $payload = [];

	protected function getGuzzleClient()
	{
		return new Client(['Content-Type' => 'application/x-www-form-urlencoded']);
	}

	private function makeRequestPayload()
	{
        $route = cs()->alipay_route;
		$config = config("alipay.{$route}");

		$this->payload = [
			'apiname' => 'com.alipay.account.auth',
			'method' => 'alipay.open.auth.sdk.code.get',
			'app_id' => $config['app_id'],
			'app_name' => 'mc',
			'biz_type' => 'openservice',
			'pid' => $config['pid'],
			'product_id' => 'APP_FAST_LOGIN',
			'scope' => 'kuaijie',
			'target_id' => mt_rand(999, 99999),
			'auth_type' => 'AUTHACCOUNT',
			'sign_type' => 'RSA2',
			'sign' => '',
		];

		if ($config['app_cert_public_key'] && $config['alipay_root_cert']) {
			$this->payload['app_cert_sn'] = $this->getCertSN($config['app_cert_public_key']);
			$this->payload['alipay_root_cert_sn'] = $this->getRootCertSN($config['alipay_root_cert']);
		}

		$this->payload['sign'] = $this->generateSign($this->payload);
	}

	protected function getCertSN($certPath): string
	{
		if (!is_file($certPath)) {
			throw new Exception('unknown certPath -- [getCertSN]');
		}
		$x509data = file_get_contents($certPath);
		if (false === $x509data) {
			throw new Exception('Alipay CertSN Error -- [getCertSN]');
		}
		openssl_x509_read($x509data);
		$certdata = openssl_x509_parse($x509data);
		if (empty($certdata)) {
			throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]');
		}
		$issuer_arr = [];
		foreach ($certdata['issuer'] as $key => $val) {
			$issuer_arr[] = $key.'='.$val;
		}
		$issuer = implode(',', array_reverse($issuer_arr));

		return md5($issuer.$certdata['serialNumber']);
	}

	protected function getRootCertSN($certPath)
	{
		if (!is_file($certPath)) {
			throw new Exception('unknown certPath -- [getRootCertSN]');
		}
		$x509data = file_get_contents($certPath);
		if (false === $x509data) {
			throw new Exception('Alipay CertSN Error -- [getRootCertSN]');
		}
		$kCertificateEnd = '-----END CERTIFICATE-----';
		$certStrList = explode($kCertificateEnd, $x509data);
		$md5_arr = [];
		foreach ($certStrList as $one) {
			if (!empty(trim($one))) {
				$_x509data = $one.$kCertificateEnd;
				openssl_x509_read($_x509data);
				$_certdata = openssl_x509_parse($_x509data);
				if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) {
					$issuer_arr = [];
					foreach ($_certdata['issuer'] as $key => $val) {
						$issuer_arr[] = $key.'='.$val;
					}
					$_issuer = implode(',', array_reverse($issuer_arr));
					if (0 === strpos($_certdata['serialNumber'], '0x')) {
						$serialNumber = self::bchexdec($_certdata['serialNumber']);
					} else {
						$serialNumber = $_certdata['serialNumber'];
					}
					$md5_arr[] = md5($_issuer.$serialNumber);
				}
			}
		}

		return implode('_', $md5_arr);
	}

	protected function getSignContent(array $data, $verify = false): string
	{
		ksort($data);

		$stringToBeSigned = '';
		foreach ($data as $k => $v) {
			if ($verify && 'sign' != $k && 'sign_type' != $k) {
				$stringToBeSigned .= $k.'='.$v.'&';
			}
			if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
				$stringToBeSigned .= $k.'='.$v.'&';
			}
		}

		return trim($stringToBeSigned, '&');
	}

	protected function getBizContent(array $params)
	{
		return json_encode($params);
	}

	protected function generateSign(array $params): string
	{
        $route = cs()->alipay_route;
        $config = config("alipay.{$route}");

		$privateKey = $config['private_key'];

		if (is_null($privateKey)) {
			throw new Exception('Missing Alipay Config -- [private_key]');
		}

		if (Str::endsWith($privateKey, '.pem')) {
			$privateKey = openssl_pkey_get_private(
				Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
			);
		} else {
			$privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
				wordwrap($privateKey, 64, "\n", true).
				"\n-----END RSA PRIVATE KEY-----";
		}

		openssl_sign($this->getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);

		$sign = base64_encode($sign);

		if (is_resource($privateKey)) {
			openssl_free_key($privateKey);
		}

		return $sign;
	}

	private static function bchexdec($hex)
	{
		/**
		 * convert to high precision number.
		 */

		$dec = 0;
		$len = strlen($hex);
		for ($i = 1; $i <= $len; ++$i) {
			if (ctype_xdigit($hex[$i - 1])) {
				$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
			}
		}

		return str_replace('.00', '', $dec);
	}

	public function appPayload()
	{
		$this->makeRequestPayload();
		return new Response(http_build_query($this->payload));
	}
}
